package org.tlang.ast.list.array;

import org.tlang.ast.AST;
import org.tlang.ast.ASTList;
import org.tlang.ast.leaf.Name;
import org.tlang.context.SymbolTable;
import org.tlang.context.TypeTable;
import org.tlang.context.ValueTable;
import org.tlang.exception.EvalException;
import org.tlang.exception.TypeException;
import org.tlang.type.*;

import java.util.List;

/**
 * example: new int[1][2]
 */
public class NewArrayInvoker extends ASTList {

    public NewArrayInvoker(List<AST> children) {
        super(children);
    }

    private String name() {
        return ((Name) child(0)).name();
    }

    private Type type() throws TypeException {
        return Type.tranStrToType(name(), this);
    }

    private int dimension() {
        return numChildren() - 1;
    }

    @Override
    public void lookup(SymbolTable symbolTable, TypeTable typeTable) {
        for (int i = 1; i < numChildren(); i++) {
            child(i).lookup(symbolTable, typeTable);
        }
    }

    @Override
    public Type typeCheck(TypeTable typeTable) throws TypeException {
        if (!isSupportType(type())) {
            throw new TypeException("not support array type: " + name(), this);
        }

        // 长度表达式计算结果类型必须为整数
        for (int i = 1; i < numChildren(); i++) {
            Type type = child(i).typeCheck(typeTable);
            if (type != IntType.INT_TYPE) {
                throw new TypeException("length expr type is not int", child(i));
            }
        }

        return new ArrayType(type(), dimension());
    }

    @Override
    public Object eval(ValueTable valueTable) {
        switch (dimension()) {
            case 1:
                return createDimOneArr((Long) child(1).eval(valueTable));
            case 2:
                return createDimTwoArr((Long) child(1).eval(valueTable),
                        (Long) child(2).eval(valueTable));
            default:
                throw new EvalException("not support array with dimension: " + dimension(), this);
        }
    }

    private Object[] createDimOneArr(Long length) {
        Type type;
        try {
            type = type();
        } catch (TypeException e) {
            throw new EvalException("type" + name() + " not support", this);
        }

        if (type.isPrimitiveType()) {
            if (type instanceof IntType) {
                return new Integer[length.intValue()];
            } else if (type instanceof StrType) {
                return new String[length.intValue()];
            } else if (type instanceof BoolType) {
                return new Boolean[length.intValue()];
            }
        }

        throw new EvalException("type" + name() + " not support", this);
    }

    private Object[][] createDimTwoArr(Long len1, Long len2) {
        Type type;
        try {
            type = type();
        } catch (TypeException e) {
            throw new EvalException("type" + name() + " not support", this);
        }

        int intLen1 = len1.intValue();
        int intLen2 = len2.intValue();

        if (type.isPrimitiveType()) {
            if (type instanceof IntType) {
                return new Long[intLen1][intLen2];
            } else if (type instanceof StrType) {
                return new String[intLen1][intLen2];
            } else if (type instanceof BoolType) {
                return new Boolean[intLen1][intLen2];
            }
        }

        throw new EvalException("type" + name() + " not support", this);
    }

    private boolean isSupportType(Type type) {
        return type instanceof IntType ||
                type instanceof StrType ||
                type instanceof BoolType;
    }

    @Override
    public String toString() {
        StringBuilder stringBuilder = new StringBuilder("new ").append(name());
        for (int i = 1; i < numChildren(); i++) {
            stringBuilder.append("[").append(child(i)).append("]");
        }
        return stringBuilder.toString();
    }
}
